#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This program is free software; you can redistribute it and/or modify  
# it under the terms of the GNU General Public License as published by  
# the Free Software Foundation; either version 2 of the License, or     
# (at your option) any later version.                                   
#                                                                         
# A copy of the license can be found in the license.txt file supplied   
# with this software or at: http://www.gnu.org/copyleft/gpl.html       
#

import time

from cell import Cell
from celtuce.event import Event, OP_CREATE_CELL, OP_INSERT, OP_ERASE, OP_INSERTUNIQUE, OP_UPDATE, OP_UPSERT

from helper import makeCellPath
from consts import TIMEOUT_WAIT_REPLY


from celtuce.response import Response,RC_TIMEOUT,RC_TABLE_NOT_FOUND,RC_CELL_NOT_FOUND,RC_OUT_OF_CELL_INTERVAL

import celtuce.help.logging as logging


class   Mutator(object) :
    def __init__(self, cell_keeper) :
        self._cell_keeper = cell_keeper

        self._id_base = id(self)
        self._id_serial = 1
        self._id_maximum = 1024 * 1024 * 1024

        self._logger = logging.getLogger('mutator')
        ## __init__()


    ####-----------------------------------------------------------####

    def insert(self, table, key, value, timestamp_ms) :
        for retry in range(11) :
            response = self.__doInsertOrUpdate(table, key, value, timestamp_ms, OP_INSERT)
            if response.isOk() :
                return response

            if response.status_code in (RC_CELL_NOT_FOUND, RC_OUT_OF_CELL_INTERVAL):
                if retry < 10 :
                    self._logger.error('Cell NOT FOUND : %s, and will retry ...', response.error)
                    time.sleep(1)
            # for    
        return response
        ## insert()

    def insertunique(self, table, key, value, timestamp_ms) :
        for retry in range(11) :
            response = self.__doInsertOrUpdate(table, key, value, timestamp_ms, OP_INSERTUNIQUE)
            if response.isOk() :
                return response

            if response.status_code in (RC_CELL_NOT_FOUND, RC_OUT_OF_CELL_INTERVAL):
                if retry < 10 :
                    self._logger.error('Cell NOT FOUND : %s, and will retry ...', response.error)
                    time.sleep(1)
            # for    
        return response
        ## insert()

    def __doInsertOrUpdate(self, table, key, value, timestamp_ms, op) :
        # STEP. find cell to host key
        response = self.lookupCell(table, key)
        if not response.isOk() :
            return response
        cell = response.body

        # STEP. find queue of cell
        response = self.lookupCellQueue(cell)
        if not response.isOk() :
            return response
        cell_queue = response.body

        # STEP. make request
        request = Event()
        request.request_id = self.genRequestID()
        request.response_to = 0
        request.source_queue = self
        request.op = op
        request.body = (table, key, value, timestamp_ms, cell.path)

        # STEP. put request into queue of cell
        reply = cell_queue.handle( request )

        # STEP. wait for reply
        response = reply.body
        if response is None :
            # timeout
            response = Response()
            response.status_code = RC_TIMEOUT
            response.error = 'Timeout to wait reply for request "%s"' % str(request)

        return response
        ## __doInsertOrUpdate()

    ####-----------------------------------------------------------####

    def erase(self, table, key, timestamp_ms) :
        for retry in range(11) :
            response = self.__doErase(table, key, timestamp_ms)
            if response.isOk() :
                return response

            if response.status_code in (RC_CELL_NOT_FOUND, RC_OUT_OF_CELL_INTERVAL):
                if retry < 10 :
                    self._logger.error('Cell NOT FOUND : %s, and will retry ...', response.error)
                    time.sleep(1)
            # for    
        return response
        ## erase()

    def __doErase(self, table, key, timestamp_ms) :
        # STEP. find cell to host key
        response = self.lookupCell(table, key)
        if not response.isOk() :
            return response
        cell = response.body

        # STEP. find queue of cell
        response = self.lookupCellQueue(cell)
        if not response.isOk() :
            return response
        cell_queue = response.body

        # STEP. make request
        request = Event()
        request.request_id = self.genRequestID()
        request.response_to = 0
        request.source_queue = self
        request.op = OP_ERASE
        request.body = (table, key, timestamp_ms, cell.path)

        # STEP. put request into queue of cell
        reply = cell_queue.handle( request )

        # STEP. wait for reply
        response = reply.body
        if response is None :
            # timeout
            response = Response()
            response.status_code = RC_TIMEOUT
            response.error = 'Timeout to wait reply for request "%s"' % str(request)

        return response
        ## __doErase()


    ####-----------------------------------------------------------####

    def lookupCell(self, table, key) :
        return self._cell_keeper.lookupCell(table, key)
        ## lookupCell()

    def lookupCellQueue(self, cell) :
        return  self._cell_keeper.lookupCellQueue(cell)
        ## lookupCellQueue()

    ####-----------------------------------------------------------####

    def createCell(self, table, key_begin, key_end) :
        # STEP. check if the tablet exists
        response = self._cell_keeper.existCell(table, key_begin, key_end)
        if response.status_code not in (RC_TABLE_NOT_FOUND, RC_CELL_NOT_FOUND) :
            # if response is NOT ok, then something is wrong
            return response

        # STEP. get the device to create cell
        response = self._cell_keeper.findAvailableDevice()
        if not response.isOk() :
            return response
        device = response.body
        cell = Cell()
        cell.table = table
        cell.key_begin = key_begin
        cell.key_end = key_end
        cell.path = makeCellPath(device.path_list[0], table, key_begin, key_end)
        cell.device = device.dev_id

        # STEP. make request
        request = Event()
        request.request_id = self.genRequestID()
        request.response_to = 0
        request.source_queue = self
        request.op = OP_CREATE_CELL
        request.body = cell

        # STEP. put request into queue of worker
        response = self._cell_keeper.lookupCellQueue( cell )
        if not response.isOk() :
            return response
        queue_worker = response.body
        result = queue_worker.handle( request )
        response = result.body

        # STEP. wait for reply
        #self._logger.debug('got response "%s" for request "%s"', response, request)
        if response is None :
            # timeout
            response = Response()
            response.status_code = RC_TIMEOUT
            response.error = 'Timeout to wait reply for request "%s"' % str(request)

        # STEP. register cell to cell table
        if response.isOk() :
            self._cell_keeper.addCell( cell )

        return  response
        ## createCell()

    def dropCell(self, table, key_begin, key_end) :
        return  self._cell_keeper.dropCell(table, key_begin, key_end)
        ## dropCell()

    ####-----------------------------------------------------------####

    def genRequestID(self) :
        # TODO: lock
        serial = self._id_serial

        self._id_serial += 1
        if self._id_serial >= self._id_maximum :
            self._id_serial = 1

        id = '.'.join((str(self._id_base), str(serial)))
        return id
        ## genRequestID()

    def waitReply(self, response_to) :
        reply = None
        while True :
            try :
                evt = self._event_queue.get(True, TIMEOUT_WAIT_REPLY)
                if evt.response_to == response_to :
                    reply = evt.body
                    self._event_queue.task_done()
                    break
                else :
                    self._event_queue.task_done()
            except queue.Empty :
                return None

        return reply
        ## waitReply()

    ## class Mutator

